import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.LinkedList;


/**
 * @author Andre Algotsson
 * The StaticMap is a SingleTon representation of the Sokoban map,
 * holding information about non-changeable elements such as board size,
 * goal tiles and wall tiles.
 */
public class StaticMap implements Drawable{
	
// Variable Initializations
	
	private static StaticMap staticMap;
	
	public int boardW;
	public int boardH;
	private int[][] tileMatrix;
	private int[][] firstTileMatrix;
	private TileList goalTiles;
	private TileList wallTiles;	
	private Utility goalUtilityMap;
	private TileList finalInstruction;
	
	private BoardState firstState;
	
	public TileList getGoalTiles() {
		return goalTiles;
	}

	public void setGoalTiles(TileList goalTiles) {
		this.goalTiles = goalTiles;
	}

	//Tiles that the player can walk on
	private static final int EMPTYTILE    		    = 0;
	private static final int GOALTILE     		    = 1;
	private static final int CORNERTILE   		    = 2;
	private static final int ADJTOWALLTILE_NS       = 3;
	private static final int ADJTOWALLTILE_EW  		= 4;
	private static final int DEATHTILE		  		= 5;
	private static final int TUNNELTILE		  		= 7;
	
	//Tiles that the player cannot walk on
	private static final int WALLTILE     		 	= 6;
	
	
	public int c=0;
	private long t1=(long) 0.0;
	private long t2=(long) 0.0;
	
	public void c(){

	}
	
	private StaticMap() {
		finalInstruction = new TileList();
		goalTiles = new TileList();
		wallTiles = new TileList();
	}
	
	public static StaticMap getStaticMap() {
	    if (staticMap == null)
	    	staticMap = new StaticMap();		
	    return staticMap;
	  }

	public static void reset() {
		staticMap = new StaticMap();
	}
	
	public void setGoalUtility(Utility goalUtility) {
		this.goalUtilityMap = goalUtility;
	}
	
	public void initBoard(int boardWidth, int boardHeight) {
		boardW = boardWidth;
		boardH = boardHeight;
		
		buildMatrix();
		//markTiles();
	}
	
	public void setFirstState(BoardState state) {
		firstState = state;
		int i=0;
		for (Box box : state.getBoxList()) {
			i++;
			Coord c = box.getCoord();
			firstTileMatrix[c.x][c.y] = i;
		}
	}
	
	public void addInstruction(Coord tile) {
		finalInstruction.add(tile);
	}
	
	public BoardState generateGoalState() {
		BoxList goalBoxlist = new BoxList();
		char[] directions = Constants.getAllDirectionsRev();
		BoardState newGoalState = null;
		GlobalCollections stateCollections = GlobalCollections.getCollection();
		Coord playerGoalPos = null;
		
		boolean foundPos = false;
		
		for (Coord goalTile : goalTiles) {
			goalBoxlist.add(new Box(goalTile));
		}
		/*
		for (Coord goalTile : goalTiles) {
			for (char dir : directions) {
				Coord adjTile = goalTile.getCoordFromDirection(dir);
				if (staticMap.isTileEmpty(adjTile)) {
					if (!goalBoxlist.isBoxOnTile(adjTile)) {
						playerGoalPos = adjTile;
						
						newGoalState = new BoardState(null, null,goalBoxlist,playerGoalPos,null);
						//if (newGoalState.existsPredeccessors()) {						
						HashCollection generatedStates = stateCollections.getBwdStates();
						if (generatedStates.retrieveState(newGoalState) == null) {
							foundPos = true;
							//playerGoalPos.print();
							break;
						}
					}
				}

			}
			if (foundPos) break;
		}*/
		
		
		foundPos = false;
		
		if (!foundPos) {
			for (int i=0 ; i<boardW ; i++) {
				for (int j=0 ; j<boardH ; j++) {
					if (staticMap.isTileEmpty(i,j)) {
						//if (!goalBoxlist.isBoxOnTile(new Coord(i,j))) {
						if (!staticMap.isTileGoal(new Coord(i,j))) {
							playerGoalPos = new Coord(i,j);
							newGoalState = new BoardState(null, null,goalBoxlist,playerGoalPos,null);
							newGoalState.setUtilityReferences(goalUtilityMap);
							
							HashCollection generatedStates = stateCollections.getBwdStates();
							if (generatedStates.retrieveState(newGoalState) == null) {
								generatedStates.insert(newGoalState);  //Experiment!
								foundPos = true;
								//playerGoalPos.print();
								break;
							}
							/*if (newGoalState.existsPredeccessors()) {						
								foundPos = true;
								break;
							}*/
						}
					}
				}
				if (foundPos) break;
			}
		}
		
		if (!foundPos) {
			System.err.println("Found no position for the player in the goal state!");
			return null;
		}

		return newGoalState;
	}
	
	/**
	 * The value to be returned
	 */
	public void buildMatrix() {
		tileMatrix = new int[boardW][boardH];
		firstTileMatrix = new int[boardW][boardH];
		//setTile(new Coord(8,8), WALLTILE);
	}
	
	public void setTile(Coord tilePos, int newValue) {
		tileMatrix[tilePos.x][tilePos.y] = newValue;
		
		switch (newValue) {
		case GOALTILE:   goalTiles.add(new Coord(tilePos)); break;
		case WALLTILE:   wallTiles.add(new Coord(tilePos)); break;
		default: 	     break;
		}
	}
	
	public void setTile(int x, int y, int newValue) {
		tileMatrix[x][y] = newValue;
		
		switch (newValue) {
		case GOALTILE:   goalTiles.add(new Coord(x,y)); break;
		case WALLTILE:   wallTiles.add(new Coord(x,y)); break;
		default: 	     break;
		}
	}
	
	
	
	/**
	 * Find the constraints of the tiles. 
	 * I.e. find corner tiles and mark them
	 */
	public void markTiles() {
		extractWallInformation1();
		findDeathWalls();
		findTunnels();
	}
	
	public void updateTile(int x, int y, int newValue) {
		if (tileMatrix[x][y] == WALLTILE) {
			System.err.println("Not allowed to change wall tiles!! Tile: " + x + " " + y);
		} else {
			tileMatrix[x][y] = newValue;
		}
	}
	
	public boolean isFirstState(BoardState state) {
		return firstState.isSameState(state);
	}
	private void findTunnels() {
		int currentTile;
		
		for (int i=0 ; i<boardW ; i++) {
			for (int j=0 ; j<boardH ; j++) {
				currentTile = tileMatrix[i][j];
				if ((currentTile != CORNERTILE)&&(currentTile != DEATHTILE)&&
						(currentTile != GOALTILE)&&(currentTile != WALLTILE)) {
					
					
					if (isTunnel(i,j, 'u')) {
						//System.out.println("Tunnel: " + i + " " + j);
						currentTile = TUNNELTILE;
						tileMatrix[i][j] = TUNNELTILE;
					} else if (isTunnel(i,j, 'r')){
						//System.out.println("Tunnel: " + i + " " + j);
						currentTile = TUNNELTILE;
						tileMatrix[i][j] = TUNNELTILE;
					}
				}
			}
		}
	}
	
	
	public boolean isTunnelOld(Coord c, char direction) {
		Coord c1, c2, c3, c4;
		
		switch(direction) {
		case 'u':
		case 'U': 
		case 'd':
		case 'D': 
			c1 = c.getEastCoord();
			c2 = c.getWestCoord();
			break;
		case 'r':
		case 'R': 
		case 'l':
		case 'L': 
			c1 = c.getNorthCoord();
			c2 = c.getSouthCoord();
			break;
		default: 
			System.err.println("Error, isTunnel: Illegal direction");
			return false;
		}
		
		if (isTileWall(c1) && isTileWall(c2)) {
			c3 = c1.getCoordFromDirection(direction);
			c4 = c2.getCoordFromDirection(direction);
			boolean tunnel = (isTileWall(c3) && isTileWall(c4));
			
			c3 = c1.getCoordFromDirectionRev(direction);
			c4 = c2.getCoordFromDirectionRev(direction);
			tunnel = tunnel && (isTileWall(c3) && isTileWall(c4));
			
			tunnel = tunnel && (!isTileWall(c.getCoordFromDirection(direction)));
			/*if (tunnel) {
				System.out.println("Found tunnel:");
				c1.print();
				c2.print();
				System.out.println("------------");
			}*/
			return tunnel;
		} else {
			return false;
		}
	}
	
	public boolean isTileTunnel(Coord c) {
		return (tileMatrix[c.x][c.y] == TUNNELTILE);
	}
	
	public boolean isTunnel(int x, int y, char direction) {
			
		WalkingCoord.set(x, y);
			
		
		switch(direction) {
		case 'u':
		case 'U': 
		case 'd':
		case 'D': 
			if (isTileWall(WalkingCoord.getFromDirection(direction))) return false;
			
			WalkingCoord.setCurrentCoordToInitial();
			
			boolean tunnel = true;
			
			for (int i=0 ; i<3 ; i++){
				if (isTileWall(WalkingCoord.getEast())&&(isTileWall(WalkingCoord.getWest()))) {
					WalkingCoord.getInitial();
					WalkingCoord.getFromDirectionRev(direction);
					WalkingCoord.setCurrentCoordToInitial();				
				} else {
					WalkingCoord.getInitial();
					WalkingCoord.getFromDirectionRev(direction);
					WalkingCoord.setCurrentCoordToInitial();
					tunnel = tunnel && false;
					
				}
			}
			
			return tunnel;
			//System.out.println("Found tunnel:");
			//c.print();
			//System.out.println("------------");

			//return true;
		case 'r':
		case 'R': 
		case 'l':
		case 'L': 
			if (isTileWall(WalkingCoord.getFromDirection(direction))) return false;
			
			WalkingCoord.setCurrentCoordToInitial();
			
			 tunnel = true;
			
			for (int i=0 ; i<3 ; i++){
				if (isTileWall(WalkingCoord.getNorth())&&(isTileWall(WalkingCoord.getSouth()))) {
					WalkingCoord.getInitial();
					WalkingCoord.getFromDirectionRev(direction);
					WalkingCoord.setCurrentCoordToInitial();				
				} else {
					WalkingCoord.getInitial();
					WalkingCoord.getFromDirectionRev(direction);
					WalkingCoord.setCurrentCoordToInitial();
					tunnel = tunnel && false;
				}
			}
			
			
			//System.out.println("Found tunnel:");
			//c.print();
			//System.out.println("------------");

			return tunnel;
		default: 
			System.err.println("Error, isTunnel: Illegal direction");
			return false;
		}

	}
	/*
	public boolean isTileFreeForBox(Coord c) {
		int tileValue = tileMatrix[c.x][c.y];
		return  ((tileValue != DEATHTILE)&&(tileValue != CORNERTILE));
	}*/
	
	
	private void findDeathWalls() {
		boolean corner1found = false;
		int deathTileCounter = 0;
		
		for (int i=0 ; i<boardW ; i++) {
			for (int j=0 ; j<boardH ; j++) {
				if (tileMatrix[i][j] == WALLTILE) {
					corner1found = false;
					deathTileCounter=0;
				} else if ((!corner1found)&&(tileMatrix[i][j] == CORNERTILE)) {
					corner1found = true;
				} else if ((corner1found)&&((tileMatrix[i][j] == ADJTOWALLTILE_NS)||(tileMatrix[i][j] == ADJTOWALLTILE_EW)||
						(tileMatrix[i][j] == DEATHTILE))) {
					deathTileCounter++;
				} else if ((corner1found)&&(tileMatrix[i][j] == CORNERTILE)) {
					for (int deathTileIndex=1 ; deathTileIndex<=deathTileCounter ; deathTileIndex++) {

						updateTile(i, j-deathTileIndex, DEATHTILE);
						//System.out.println("Deadlock: " + i + " " + (j-deathTileIndex));
					}
					deathTileCounter=0;
					corner1found = false;
				} else {
					deathTileCounter=0;
					corner1found = false;
				}
			}
		}
		
		corner1found = false;
		deathTileCounter=0;
		
		for (int i=0 ; i<boardH ; i++) {
			for (int j=0 ; j<boardW ; j++) {	
				if (tileMatrix[j][i] == WALLTILE) {
					corner1found = false;
					deathTileCounter=0;
				} else if ((!corner1found)&&(tileMatrix[j][i] == CORNERTILE)) {
					corner1found = true;
				} else if ((corner1found)&&
						((tileMatrix[j][i] == ADJTOWALLTILE_NS)||(tileMatrix[j][i] == ADJTOWALLTILE_EW)||
								(tileMatrix[j][i] == DEATHTILE))) {
					deathTileCounter++;
				} else if ((corner1found)&&(tileMatrix[j][i] == CORNERTILE)) {
					for (int deathTileIndex=1 ; deathTileIndex<=deathTileCounter ; deathTileIndex++) {
						updateTile(j-deathTileIndex, i, DEATHTILE);
						//System.out.println("Deadlock: " + (j-deathTileIndex) + " " + i);
					}
					deathTileCounter=0;
					corner1found = false;
				} else {
					deathTileCounter=0;
					corner1found = false;
				}
			}
		}
	}
	
	private void extractWallInformation1() {
		int x=0; int y=0;
		for(Coord tile : wallTiles) {	

			//if(x==5 && y==11)System.err.println("<<<<<<<<<<<<<<<<<<<MUERTE>>>>>>>>>>>");

			x = tile.x;
			y = tile.y+1;
			
			//if(x==5 && y==11)System.err.println("HIYA");
			//if(x==5 && y==12)System.err.println("HIYA2");
			//if(x==5 && y==13)System.err.println("HIYA3");
			
			//Mark the vertical tiles
			for (int i=0 ; i<2 ; i++) {
				
				if ((y<boardH)&&(y>=0)) {
					if (tileMatrix[x][y] == EMPTYTILE) {
						updateTile(x, y, ADJTOWALLTILE_NS);
					} else if  (tileMatrix[x][y] == ADJTOWALLTILE_EW) {
						updateTile(x, y, CORNERTILE);
						//System.out.println("corner! " + x + " " + y);
					}
				}
			
				y = tile.y-1;
			}
			
			x = tile.x+1;
			y = tile.y;
			
			//Mark the horizontal tiles
			for (int i=0 ; i<2 ; i++) {
				
				if ((x<boardW)&&(x>=0)) {
					if (tileMatrix[x][y] == EMPTYTILE) {
						updateTile(x, y, ADJTOWALLTILE_EW);
					} else if  (tileMatrix[x][y] == ADJTOWALLTILE_NS) {
						updateTile(x, y, CORNERTILE);
						//System.out.println("corner! " + x + " " + y);
					}
				}
			
				x = tile.x-1;
			}
			
			x = tile.x;
		}
	}
	
	/**
	 * VERY IMPORTANT: Tile is empty if the player can walk on it. The wall tile is the only non-walkable tile
	 * @param tile
	 * @return
	 */
	public boolean isTileEmpty(Coord tile) {
		int x = tile.x;
		int y = tile.y;
		
		//if(x==5 && y==11)System.err.println("EMPTY?");
		//if(x==5 && y==12)System.err.println("HIYA2");
		//if(x==5 && y==13)System.err.println("HIYA3");

		if ((x>=0)&&(y>=0)&&(x<boardW)&&(y<boardH)) {
			//int currentTile = tileMatrix[x][y];
			return (tileMatrix[x][y] != WALLTILE);
			//return ((currentTile == EMPTYTILE)||(currentTile == GOALTILE)||(currentTile == ADJTOWALLTILE));
		}
		return false;
	}
	
	public boolean isTileEmpty(int x, int y) {
		if ((x>=0)&&(y>=0)&&(x<boardW)&&(y<boardH)) {
			//int currentTile = tileMatrix[x][y];
			return (tileMatrix[x][y] != WALLTILE);
			//return ((currentTile == EMPTYTILE)||(currentTile == GOALTILE)||(currentTile == ADJTOWALLTILE));
		}
		return false;
	}
	
	public boolean isTileLegalForBox(Coord c) {
		int x = c.x;
		int y = c.y;
		
		//if(x==5 && y==11)System.err.println("LEGAL");


		if ((x>=0)&&(y>=0)&&(x<boardW)&&(y<boardH)) {
			int tileValue = tileMatrix[x][y];
			return ((tileValue == DEATHTILE)||(tileValue == CORNERTILE));
		}
		return false;
	}
	
	public boolean isDeathWallTile(Coord tile) {
		int x = tile.x;
		int y = tile.y;
		
		//if(x==5 && y==11)System.err.println("DEATH");


		if ((x>=0)&&(y>=0)&&(x<boardW)&&(y<boardH)) {
			return (tileMatrix[x][y] == DEATHTILE);
		}
		return false;
	}
	
	public boolean isTileCorner(Coord tile) {
		int x = tile.x;
		int y = tile.y;
		
		//if(x==5 && y==11)System.err.println("CORNER");


		if ((x>=0)&&(y>=0)&&(x<boardW)&&(y<boardH)) {
			return (tileMatrix[x][y] == CORNERTILE);
		}
		return false;
	}
	
	public boolean isAdjacentToWallTile(Coord tile) {
		int x = tile.x;
		int y = tile.y;

		if ((x>=0)&&(y>=0)&&(x<boardW)&&(y<boardH)) {
			return ((tileMatrix[x][y] == ADJTOWALLTILE_EW)||((tileMatrix[x][y] == ADJTOWALLTILE_NS)));
		}
		return false;
	}
	
	public boolean isTileGoal(Coord tile) {
		int x = tile.x;
		int y = tile.y;

		if ((x>=0)&&(y>=0)&&(x<boardW)&&(y<boardH)) {
			return (tileMatrix[x][y] == GOALTILE);
		}
		return false;
	}

	public boolean isTileWall(Coord tile) {
		int x = tile.x;
		int y = tile.y;
		
		//if(x==5 && y==11)System.err.println("WALL");
	



		if ((x>=0)&&(y>=0)&&(x<boardW)&&(y<boardH)) {
			
			return (tileMatrix[x][y] == WALLTILE);
		}
		
		return false;
	}
	
	public void paint(Graphics g, int tileSize) {
		// TODO Auto-generated method stub
		g.setColor(Color.LIGHT_GRAY);
		g.fillRect(0, 0, tileSize*boardW, tileSize*boardH);
		g.setColor(Color.BLACK);
		g.drawRect(0, 0, tileSize*boardW, tileSize*boardH);
		
		Color color;
		
		for (int x=0 ; x<boardW ; x++) {
			for (int y=0 ; y<boardH ; y++) {
				switch (tileMatrix[x][y]) {
				case GOALTILE:   color = Color.WHITE; break;
				case EMPTYTILE:  color = Color.LIGHT_GRAY; break;
				case WALLTILE:   color = Color.black; break;
				default: 	     color = Color.LIGHT_GRAY; break;
				}
				g.setColor(color);
				g.fillRect(x*tileSize, y*tileSize, tileSize, tileSize);
				g.setColor(Color.gray);
				g.drawRect(x*tileSize, y*tileSize, tileSize, tileSize);
			}
		}

		
	}
	
	
}
